/*
 * si2c.c
 *
 * software i2c driver.
 *
 * Copyright (C) 2022 Microwell, Inc.
 * Subject to the GNU Public License, version 2.
 *
 * Author: guoming<guoming@microwell.net>
 */
#include "si2c.h"
#include "core.h"

#if (CONFIG_USING_SI2C > 0)       
#define SCL_HIGH()          (gpio_output(IIC_CLK, 1))
#define SCL_LOW()           (gpio_output(IIC_CLK, 0))
#define SCL_PULLUP()        (gpio_pullup(IIC_CLK, 1))

#define SDA_HIGH()          (gpio_output(IIC_DAT, 1))
#define SDA_LOW()           (gpio_output(IIC_DAT, 0))
#define SDA_INPUT_MODE()    (gpio_input(IIC_DAT))
#define SDA_PULLUP()        (gpio_pullup(IIC_DAT, 1))
#if (IIC_DAT > 0x14)
#define SDA_STATUS()        (!!(READ_EFR_REG(PEXTCON0) & (1 << (IIC_DAT & 0x0f) - 5)))
#elif (IIC_DAT > 0x08)
#define SDA_STATUS()        (!!(P1 & (1 << (IIC_DAT & 0x0f))))
#else
#define SDA_STATUS()        (!!(P0 & (1 << (IIC_DAT & 0x0f))))
#endif

#define SI2C_DELAY()            sys_delay_10us(1)

/* SDA HIGH to LOW while SCL is HIGH */
static void si2c_start(void)   
{
    SDA_HIGH();

    SCL_HIGH();
    SI2C_DELAY();

    SDA_LOW();
    SI2C_DELAY();

    SCL_LOW();
    return;
}

/* SDA LOW to HIGH while SCL is HIGH */
static void si2c_stop(void)   
{
    SCL_LOW();
    SDA_LOW();

    SI2C_DELAY();

    SCL_HIGH();
    SDA_HIGH();
    SI2C_DELAY();

    return;
}

static int8_t si2c_wait_ack(void)
{
    uint8_t ack_times = 0;
    int8_t ret = 0;

    SDA_INPUT_MODE();
    SI2C_DELAY();

    SCL_HIGH();
    SI2C_DELAY();

    ack_times = 0;
    while (SDA_STATUS()) {
        ack_times++;
        if (ack_times == 10) {
            ret = -1;
            break;
        }
    }

    SCL_LOW();
    
    return ret;
}

static void si2c_send_ack(uint8_t state)
{
    SCL_LOW();
    if (state == 0)
        SDA_LOW();
    else
        SDA_HIGH();

    SI2C_DELAY();

    SCL_HIGH();
    SI2C_DELAY();

    SCL_LOW();
}

static void si2c_write_byte(uint8_t val)   
{
    uint8_t ibit;   
    int8_t ret = 0;

    /*
     * An I2C output byte is bits 7-0 (MSB to LSB). Shift one bit at a time to 
     * the SDATA output, and then clock the data to the I2C Slave device.    
     * Send 8 bits out the port.
     */
    SCL_LOW();

    for (ibit = 0; ibit < 8; ibit++) {
        if (val & 0x80) {
            SDA_HIGH();
        } else {
            SDA_LOW();
        }
        SI2C_DELAY();
             
        SCL_HIGH();
        SI2C_DELAY();

        SCL_LOW();
        SI2C_DELAY();

        val <<= 1;
    }
}

static uint8_t si2c_read_byte(void)
{
    uint8_t ret = 0;
    uint8_t i = 0;

    SDA_INPUT_MODE();
    for (i = 0; i < 8; i++) {
        SCL_LOW();
        SI2C_DELAY();

        SCL_HIGH();
        SI2C_DELAY();
        ret <<= 1;
        if (SDA_STATUS()) {
            ret |= 0x01;
        }
    }

    return ret;
}

void si2c_master_init()
{
    SCL_PULLUP();
    SCL_HIGH();

    SDA_PULLUP();
    SDA_HIGH();
}

void si2c_master_exit(void)
{
}

int8_t si2c_master_write(uint8_t device_addr, uint8_t device_reg, uint8_t val)
{
    debug_print("si2c_mw\n", 0);

    si2c_start();
    si2c_write_byte(device_addr);
    if (si2c_wait_ack() < 0) {
        debug_print("si2c_mw: write addr no-ack\n", 0);
        goto error;
    }

    si2c_write_byte(device_reg);
    if (si2c_wait_ack() < 0) {
        debug_print("si2c_mw: write reg no-ack\n", 0);
        goto error;
    }

    si2c_write_byte(val);
    if (si2c_wait_ack() < 0) {
        debug_print("si2c_mw: write val no-ack\n", 0);
        goto error;
    }

    si2c_stop();
    return 0;

error:
    si2c_stop();
    return -1;
}


int16_t si2c_master_read(uint8_t device_addr, uint8_t device_reg)
{
    int16_t val = -1;

    debug_print("si2c_mr\n", 0);

    si2c_start();
    si2c_write_byte(device_addr);
    if (si2c_wait_ack() < 0) {
        debug_print("si2c_mr: write addr-w no-ack\n", 0);
        goto error;
    }

    si2c_write_byte(device_reg);
    if (si2c_wait_ack() < 0) {
        debug_print("si2c_mr: write reg no-ack\n", 0);
        goto error;
    }

    si2c_start();
    si2c_write_byte(device_addr + 1);
    if (si2c_wait_ack() < 0) {
        debug_print("si2c_mr: write addr-r no-ack\n", 0);
        goto error;
    }

    val = si2c_read_byte();

    si2c_send_ack(1);

error:
    si2c_stop();
    
    return val;    
}
#endif